"""
web server
模型： 基于epoll的IO多路复用并发
"""
from socket import *
from select import *
import re

# 具体处理浏览器事物
class HTTPHandle:
    def __init__(self, html=None):
        self.html = html

    # 解析请求内容
    def _get_info(self, request):
        patter = r"[A-Z]+\s+(/\S*)"
        result = re.match(patter, request)
        if result:
            return result.group(1)  # 只获取子组所对应的内容

    def handle(self, conn):
        request = conn.recv(1024).decode()
        # 解析请求内容  info / None
        info = self._get_info(request)
        if not info:
            raise Exception  # 抛出异常
        print("请求内容：", info)  # 提示请求内容
        self._send_reponse(info, conn)  # 组织响应内容发送给浏览器

    # 组织http响应
    def _send_reponse(self, info, conn):
        if info == '/':
            filename = self.html + '/index.html'  # 主页
        else:
            filename = self.html + info  # 其他
        try:
            file = open(filename, 'rb')
        except:
            # 文件不存在
            response = "HTTP/1.1 404 Not Found\r\n"
            response += "Content-Type:text/html\r\n"
            response += "\r\n"
            response += "Sorry....."
            response = response.encode()
        else:
            # 文件存在
            response = "HTTP/1.1 200 OK\r\n"
            response += "Content-Type:text/html\r\n"
            response += "\r\n"
            response = response.encode() + file.read()
        finally:
            conn.send(response)  # 发送响应

# IO 网络并发服务
class WebServer:
    def __init__(self, host='', port=0, html=None):
        self.host = host
        self.port = port
        self.html = html
        self.address = (self.host, self.port)
        self.sock = self._create_socket()
        self.ep = epoll()
        self.map = {}  # 存放查找键值对
        self.httpd = HTTPHandle(self.html)

    def _create_socket(self):
        sock = socket()
        sock.bind(self.address)
        sock.setblocking(False)  # 非阻塞
        return sock

    def _connect(self, fd):
        # 处理客户端连接事情
        conn, addr = self.map[fd].accept()
        print("Connect from", addr)
        conn.setblocking(False)
        # 添加关注
        self.ep.register(conn, EPOLLIN)
        self.map[conn.fileno()] = conn  # 维护字典

    def start(self):
        self.sock.listen(5)
        print("Listen the port %d" % self.port)
        self.ep.register(self.sock, EPOLLIN)
        self.map[self.sock.fileno()] = self.sock
        # 循环处理发生的事件
        while True:
            events = self.ep.poll()  # [(fileno,event),()]
            # 循环遍历，分情况讨论
            for fd, event in events:
                if fd == self.sock.fileno():
                    self._connect(fd)
                else:
                    # 跨类调用
                    try:
                        self.httpd.handle(self.map[fd])
                    except Exception as e:
                        print(e)
                    finally:
                        self.ep.unregister(fd)
                        self.map[fd].close()
                        del self.map[fd]


if __name__ == '__main__':
    web = WebServer(host="0.0.0.0", port=8888, html="./static")
    web.start()
